<?php
namespace app\common\util;

use app\common\model\trSystemSet;
use think\facade\Env;

class WxPay
{

    protected $config;

    function __construct()
    {
        $this->config = trSystemSet::getValue("wxAppConfig");
    }

    //微信小程序接口
    public function toPay($param)
    {
        //统一下单接口
        $unifiedOrder = $this->unifiedOrder($param);
        if(empty($unifiedOrder['prepay_id'])){
            $returnData = ErrorCode::code("fail");
            $returnData["msg"] = isset($unifiedOrder['return_msg']) ? $unifiedOrder['return_msg'] : $unifiedOrder;
            return $returnData;
        }
        $parameters = array(
            'appId' => $this->config["appId"], //小程序ID
            'timeStamp' => '' . time() . '', //时间戳
            'nonceStr' => $this->createNoncestr(), //随机串
            'package' => 'prepay_id=' . $unifiedOrder['prepay_id'], //数据包
            'signType' => 'MD5'//签名方式
        );
        //签名
        $parameters['paySign'] = $this->getSign($parameters);
        return $parameters;
    }

    //统一下单接口
    private function unifiedOrder($data)
    {
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        if(request()->server("IS_SCF")) {
            $notifyUrl = 'https://' . request()->host() . "/web/notify"; //通知地址  确保外网能正常访问
        }else{
            $notifyUrl = request()->domain() . __PUBLIC__ . "/web/notify"; //通知地址  确保外网能正常访问
        }
        $parameters = array(
            'appid' => $this->config["appId"], //小程序ID
            'mch_id' => $this->config["payMch"], //商户号
            'nonce_str' => $this->createNoncestr(), //随机字符串
            'body' => $data['body'],
            'out_trade_no' => $data['out_trade_no'],
            'total_fee' => $data['total_fee'],
            'spbill_create_ip' => $_SERVER['REMOTE_ADDR'], //终端IP
            'notify_url' => $notifyUrl, //通知地址  确保外网能正常访问
            'openid' => $data['openid'], //用户id
            'trade_type' => 'JSAPI'//交易类型
        );
        //统一下单签名
        $parameters['sign'] = $this->getSign($parameters);
        $result = $this->postXmlCurl($this->arrayToXml($parameters), $url, 60);
        if(empty($result)){
            return '';
        }
        $return = $this->xmlToArray($result);
        return $return;
    }

    /**
     * @param $xml
     * @param $url
     * @param int $second
     * @param bool $useCert
     * @return mixed|string
     */
    private function postXmlCurl($xml, $url, $second = 30, $useCert = false)
    {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //严格校验
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);

        if($useCert == true){
            //设置证书
            if(request()->server("IS_SCF")) {
                $certAddress = "/tmp/apivlient_cert_car.pem";
                $keyAddress = "/tmp/appiclient_key_car.pem";
            }else {
                if (!is_dir(Env::get("root_path") . "/cert")) {
                    mkdir(Env::get("root_path") . "/cert");
                }
                $certAddress = Env::get("root_path") . "/cert/apivlient_cert.pem";
                $keyAddress = Env::get("root_path") . "/cert/appiclient_key.pem";
            }
            file_put_contents($certAddress, $this->config["certPem"]);
            file_put_contents($keyAddress, $this->config["keyPem"]);
            //使用证书：cert 与 key 分别属于两个.pem文件
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT, $certAddress);
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY, $keyAddress);
        }
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
        curl_setopt($ch, CURLOPT_TIMEOUT, 40);
        set_time_limit(0);
        //运行curl
        $data = curl_exec($ch);
        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            Tools::writeLog(request()->domain()."  支付调用curl错误：<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a>错误码:$error ；".var_export(@curl_getinfo($ch), 1));
            curl_close($ch);
            return "";
        }
    }

    //数组转换成xml
    private function arrayToXml($arr)
    {
        $xml = "<root>";
        foreach ($arr as $key => $val) {
            if (is_array($val)) {
                $xml .= "<" . $key . ">" . $this->arrayToXml($val) . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            }
        }
        $xml .= "</root>";
        return $xml;
    }

    //xml转换成数组
    private function xmlToArray($xml)
    {
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $xmlstring = @simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        $val = json_decode(json_encode($xmlstring), true);
        return $val;
    }

    //作用：产生随机字符串，不长于32位
    private function createNoncestr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    //作用：生成签名
    private function getSign($Obj)
    {
        foreach ($Obj as $k => $v) {
            $Parameters[$k] = $v;
        }
        //签名步骤一：按字典序排序参数
        ksort($Parameters);
        $String = $this->formatBizQueryParaMap($Parameters, false);
        //签名步骤二：在string后加入KEY
        $String = $String . "&key=" . $this->config["paySecret"];
        //签名步骤三：MD5加密
        $String = md5($String);
        //签名步骤四：所有字符转为大写
        $result_ = strtoupper($String);
        return $result_;
    }

    ///作用：格式化参数，签名过程需要使用
    private function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if ($urlencode) {
                $v = urlencode($v);
            }
            $buff .= $k . "=" . $v . "&";
        }
        $reqPar = '';
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }

    //提现
    public function transfer($data){
        $url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
        $money = number_format($data['total_fee'],2,'.','');

        //使用统一支付接口
        $transferData = [
            'mch_appid' => $this->config["appId"],
            'mchid' => $this->config["payMch"],
            'nonce_str' => $this->createNoncestr(),
            'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
            'check_name' => "NO_CHECK",
            'partner_trade_no' => $data['out_trade_no'],
            'openid' => $data['openid'],
            'amount' => $money * 100,
            'desc' => "钱包提现",
        ];
        $transferData['sign'] = $this->getSign($transferData);
        $xmlData = $this->arrayToXml($transferData);
        $result = $this->xmlToArray($this->postXmlCurl($xmlData, $url, 60, true));
//        Tools::writeLog("微信提现返回的参数：". var_export($result, true), "transfer.txt");
        if ($result["return_code"] == "FAIL") {
            return ['code' => 0, 'msg' => $result['return_msg']];
        } elseif ($result["result_code"] == "FAIL") {
            return ['code' => 0, 'msg' => $result['err_code_des']];
        } elseif ($result["return_code"] == "SUCCESS") {
            $result['money'] = $money;
            return ['code' => 1, 'response' => $result];
        }
        return $result;
    }

    // 微信订单退款原路退回
    public function doRefund($data, $type = 1)
    {
        //使用统一支付接口
        $refundData = [
            'appid' => $this->config["appId"],
            'mch_id' => $this->config["payMch"],
            'nonce_str' => $this->createNoncestr(),
            'out_refund_no' => $data['outRefundNo'],
            'out_trade_no' => $data['outTradeNo'],
            'total_fee' => $data['totalMoney'] * 100,
            'refund_fee' => $data['refundFee'] * 100,
            'refund_account' => $type == "1" ? 'REFUND_SOURCE_RECHARGE_FUNDS' : "REFUND_SOURCE_UNSETTLED_FUNDS" // 可用余额退款  -- 未结算资金退款
        ];

        $url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
        $refundData['sign'] = $this->getSign($refundData);
        $xmlRes = $this->postXmlCurl($this->arrayToXml($refundData), $url, 60, true);
        $result = $this->xmlToArray($xmlRes);
//        Tools::writeLog("微信退款返回的参数：". var_export($result, true)."===".var_export($xmlRes, true), "refund.txt");
        $msg = "系统异常";
        if ($result["return_code"] == "FAIL") {
            $msg = $result['return_msg'];
        } elseif ($result["result_code"] == "FAIL") {
            if($result['err_code'] == "NOTENOUGH" && $type == "1"){ //余额不足
                $res = $this->doRefund($data, 2);
                if(isset($res["code"])){
                    $msg = $res['msg'];
                }else {
                    return $res;
                }
            }else {
                $msg = $result['err_code_des'];
            }
        } elseif ($result["return_code"] == "SUCCESS" && $result['result_code'] == 'SUCCESS') {
            return ['refund_fee' => $result['total_fee']/100, 'out_trade_no' => $result['refund_id']];
        }
        $returnData = ErrorCode::code("fail");
        $returnData["msg"] = $msg;
        return $returnData;
    }
}